﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace PloobsEngine.SceneControl
{
    public class GBlurCPUPostEffect : IPostEffect
    {
        private int radius;
        private float amount;
        private float sigma;
        private float[] kernel;
        private Vector2[] offsetsHoriz;
        private Vector2[] offsetsVert;
        private Texture2D tex = null;

        public Texture2D ImageToBlur
        {
            get { return tex; }
            set { tex = value; }
        }

        /// <summary>
        /// Returns the radius of the Gaussian blur filter kernel in pixels.
        /// </summary>
        public int Radius
        {
            get { return radius; }
        }

        /// <summary>
        /// Returns the blur amount. This value is used to calculate the
        /// Gaussian blur filter kernel's sigma value. Good values for this
        /// property are 2 and 3. 2 will give a more blurred result whilst 3
        /// will give a less blurred result with sharper details.
        /// </summary>
        public float Amount
        {
            get { return amount; }
        }

        /// <summary>
        /// Returns the Gaussian blur filter's standard deviation.
        /// </summary>
        public float Sigma
        {
            get { return sigma; }
        }

        /// <summary>
        /// Returns the Gaussian blur filter kernel matrix. Note that the
        /// kernel returned is for a 1D Gaussian blur filter kernel matrix
        /// intended to be used in a two pass Gaussian blur operation.
        /// </summary>
        public float[] Kernel
        {
            get { return kernel; }
        }

        /// <summary>
        /// Returns the texture offsets used for the horizontal Gaussian blur
        /// pass.
        /// </summary>
        public Vector2[] TextureOffsetsX
        {
            get { return offsetsHoriz; }
        }

        /// <summary>
        /// Returns the texture offsets used for the vertical Gaussian blur
        /// pass.
        /// </summary>
        public Vector2[] TextureOffsetsY
        {
            get { return offsetsVert; }
        }

        public void ComputeKernel(int blurRadius, float blurAmount)
        {
            radius = blurRadius;
            amount = blurAmount;

            kernel = null;
            kernel = new float[radius * 2 + 1];
            sigma = radius / amount;

            float twoSigmaSquare = 2.0f * sigma * sigma;
            float sigmaRoot = (float)Math.Sqrt(twoSigmaSquare * Math.PI);
            float total = 0.0f;
            float distance = 0.0f;
            int index = 0;

            for (int i = -radius; i <= radius; ++i)
            {
                distance = i * i;
                index = i + radius;
                kernel[index] = (float)Math.Exp(-distance / twoSigmaSquare) / sigmaRoot;
                total += kernel[index];
            }

            for (int i = 0; i < kernel.Length; ++i)
                kernel[i] /= total;
        }
        public void ComputeOffsets(float textureWidth, float textureHeight)
        {
            offsetsHoriz = null;
            offsetsHoriz = new Vector2[radius * 2 + 1];

            offsetsVert = null;
            offsetsVert = new Vector2[radius * 2 + 1];

            int index = 0;
            float xOffset = 1.0f / textureWidth;
            float yOffset = 1.0f / textureHeight;

            for (int i = -radius; i <= radius; ++i)
            {
                index = i + radius;
                offsetsHoriz[index] = new Vector2(i * xOffset, 0.0f);
                offsetsVert[index] = new Vector2(0.0f, i * yOffset);
            }
        }

        public void PerformGaussianBlur(IRenderHelper  rh,
                                             RenderTarget2D renderTarget1)    
        {            
            
            effect.Parameters["weights"].SetValue(kernel);
            effect.Parameters["halfPixel"].SetValue(EngineStuff.HalfPixel);
            

            rh.SetUpRenderTarget(renderTarget1);
            // Perform horizontal Gaussian blur.
            if(tex !=null)
                effect.Parameters["SourceTexture0"].SetValue(tex);
            else
                effect.Parameters["SourceTexture0"].SetValue(rh[PrincipalConstants.CurrentImage]);
            effect.Parameters["offsets"].SetValue(offsetsHoriz);
            rh.RenderTextureToFullScreenVertexPixel(effect);
            outputTexture = rh.GetResultsRenderTarget();
            
            // Perform vertical Gaussian blur.                        
            effect.Parameters["SourceTexture0"].SetValue(outputTexture);
            effect.Parameters["offsets"].SetValue(offsetsVert);
            rh.RenderTextureToFullScreenVertexPixel(effect);            
            
        }

        private Effect effect;
        private EngineStuff engine;
        private RenderTarget2D rt1;        
        private const int BLUR_RADIUS = 7;
        private const float BLUR_AMOUNT = 2.0f;
        Texture2D outputTexture = null;        
        #region IPostEffect Members

        public void init(EngineStuff engine)
        {
            this.engine = engine;
            effect = EngineStuff.InternalContentManager.GetAsset<Effect>("gcpublur");
            rt1 = EngineStuff.GetDefaultColorBuffer();            
            ComputeKernel(BLUR_RADIUS, BLUR_AMOUNT);
            ComputeOffsets(EngineStuff.BackBufferWidth, EngineStuff.BackBufferHeight);            
        }

        public void Draw(IRenderHelper rHelper, Microsoft.Xna.Framework.GameTime gt, Microsoft.Xna.Framework.Rectangle screenRec, IWorld world)
        {
            PerformGaussianBlur(rHelper, rt1);
        }

        #endregion
    }
}
